package com.zhixue.media.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.j256.simplemagic.*;
import com.zhixue.exception.ZhixuePlusException;
import com.zhixue.media.dto.*;
import com.zhixue.media.mapper.*;
import com.zhixue.media.po.*;
import com.zhixue.media.service.MediaFilesService;
import com.zhixue.model.*;
import io.minio.*;
import io.minio.messages.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.*;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.*;

/**
 * @Auther: 李 力
 * @Date: 2025/4/14
 * @Description: com.zhixue.service.impl
 * @version: 1.0
 */
@Service
@Slf4j
public class MediaFilesServiceImpl extends ServiceImpl<MediaFilesMapper, MediaFiles> implements MediaFilesService {

    @Autowired
    private MinioClient minioClient;

    //普通文件
    @Value("${minio.bucket.files}")
    private String files;

    //视频
    @Value("${minio.bucket.videofiles}")
    private String videoFiles;

    @Autowired
    private MediaFilesMapper mediaFilesMapper;

    @Autowired
    private MediaFilesService mediaFilesService;

    @Autowired
    private MediaProcessMapper processMapper;

    @Autowired
    private MediaProcessHistoryMapper historyMapper;

    @Override
    public UploadFileResultDto upload(MultipartFile multipartFile, Long companyId, String objectName) {
        try {
            //准备文件基本信息
            UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
            uploadFileParamsDto.setFilename(multipartFile.getOriginalFilename());
            uploadFileParamsDto.setFileSize(multipartFile.getSize());
            uploadFileParamsDto.setFileType("001001");
            File tempFile = File.createTempFile("minio", ".temp");
            multipartFile.transferTo(tempFile);
            String localFilePath = tempFile.getAbsolutePath();
            String filename = uploadFileParamsDto.getFilename();
            //获取扩展名
            String substring = filename.substring(filename.lastIndexOf("."));
            //根据拓展名获取mimetype
            String mimeType = getMimeType(substring);
            //object
            String object = getObject();
            //文件的MD5值
            String md5 = getMd5(new File(localFilePath));
            if (objectName == null) {
                objectName = object + md5 + substring;
            }
            //上传文件到minio
            boolean result = upLoadToMinio(files, localFilePath, objectName, mimeType);
            if (result) {
                //入库
                MediaFiles mediaFiles = mediaFilesService.addMediaFilesToDb(companyId, md5, uploadFileParamsDto, files, objectName);
                UploadFileResultDto dto = new UploadFileResultDto();
                BeanUtils.copyProperties(mediaFiles, dto);
                return dto;
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return null;
    }

    //根据拓展名获取mimetype
    private String getMimeType(String str) {
        if (str == null) {
            str = "";
        }
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(str);
        String value = MediaType.APPLICATION_OCTET_STREAM_VALUE;
        if (extensionMatch != null) {
            value = extensionMatch.getMimeType();
        }
        return value;
    }

    //将文件上传到minio
    @Override
    public boolean upLoadToMinio(String bucket, String path, String object, String type) {
        try {
            UploadObjectArgs build = UploadObjectArgs.builder()
                    .bucket(bucket)
                    .filename(path)
                    .object(object)
                    .contentType(type)
                    .build();
            minioClient.uploadObject(build);
            return true;
        } catch (Exception exception) {
            exception.printStackTrace();
            log.error("上传文件出错:,bucket:{},objectName:{},错误信息:{}", bucket, object, exception.getMessage());
        }
        return false;
    }

    //获取年月日
    private String getObject() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        return (simpleDateFormat.format(new Date()).replace("-", "/")) + "/";
    }

    //获取文件的Md5
    private String getMd5(File file) {
        try {
            FileInputStream fileInputStream = new FileInputStream(file);
            return DigestUtils.md5Hex(fileInputStream);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /*
     * 在一个方法中db操作如果和网络传输有关联的，那么尽量把大方法分解，事务尽量控制在DB上
     * 上面的upload去掉 @Transactional,加在下面的方法中，在下面代码制造异常后，再次调用upload方法
     * 发现事务失效了?
     *     因为在upload里面调用addMediaFilesToDb实际上是"this"，但是这个this不是一个代理对象，要想事务生效
     * 那么调用addMediaFilesToDb的必须是个代理对象。
     * 解决办法？
     *     手动注入service来调用
     *
     * */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MediaFiles addMediaFilesToDb(Long companyId, String fileMd5, UploadFileParamsDto uploadFileParamsDto, String bucket, String objectName) {
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if (mediaFiles == null) {
            mediaFiles = new MediaFiles();
            BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles);
            mediaFiles.setId(fileMd5);
            mediaFiles.setCompanyId(companyId);
            mediaFiles.setBucket(bucket);
            mediaFiles.setFilePath(objectName);
            mediaFiles.setFileId(fileMd5);
            mediaFiles.setUrl("/" + bucket + "/" + objectName);
            mediaFiles.setStatus("1");
            mediaFiles.setAuditStatus("002003");
            mediaFilesMapper.insert(mediaFiles);
        }
        //记录待处理的任务
        //如果是avi视频才写入待处理任务
        //通过mineType判断视频类型
        addTask(mediaFiles);
        return mediaFiles;
    }

    private void addTask(MediaFiles mediaFiles) {
        String filename = mediaFiles.getFilename();
        String substring = filename.substring(filename.lastIndexOf("."));
        String mimeType = getMimeType(substring);
        if ("video/x-msvideo".equals(mimeType)) {
            //avi，写入待处理任务表
            MediaProcess mediaProcess = new MediaProcess();
            BeanUtils.copyProperties(mediaFiles, mediaProcess);
            mediaProcess.setCreateDate(new Date());
            mediaProcess.setStatus("1"); //未处理
            mediaProcess.setFailCount(0);
            mediaProcess.setUrl(null);
            processMapper.insert(mediaProcess);
        }
    }

    @Override
    public PageResult<MediaFiles> selectPageList(Long companyId, PageParams pageParams, QueryMediaParamsDto paramsDto) {
        LambdaQueryWrapper<MediaFiles> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.isNotEmpty(paramsDto.getFilename()), MediaFiles::getFilename, paramsDto.getFilename())
                .eq(StringUtils.isNotEmpty(paramsDto.getFileType()), MediaFiles::getFileType, paramsDto.getFileType())
                .eq(StringUtils.isNotEmpty(paramsDto.getAuditStatus()), MediaFiles::getAuditStatus, paramsDto.getAuditStatus());
        Page<MediaFiles> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
        Page<MediaFiles> pageResult = mediaFilesMapper.selectPage(page, wrapper);
        return new PageResult<>(pageResult.getRecords(), pageResult.getTotal(), pageParams.getPageNo(), pageParams.getPageSize());
    }

    @Override
    public RestResponse<Boolean> checkFile(String fileMd5) {
        //先查询数据库
        if (StringUtils.isEmpty(fileMd5)) {
            throw new ZhixuePlusException("参数为null");
        }
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if (mediaFiles != null) {
            //桶
            String bucket = mediaFiles.getBucket();
            //objectName
            String objectName = mediaFiles.getFilePath();
            //查询minio
            GetObjectArgs build = GetObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .build();
            try {
                FilterInputStream filterInputStream = minioClient.getObject(build);
                if (filterInputStream != null) {
                    //文件存在
                    return RestResponse.success(true);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //报错了，文件不存在
        return RestResponse.success(false);
    }

    //查询分块文件
    @Override
    public RestResponse<Boolean> checkChunk(String fileMd5, Integer chunk) {
        if (StringUtils.isEmpty(fileMd5) || chunk == null) {
            throw new ZhixuePlusException("参数为null");
        }
        String chunkFilePath = getChunkFilePath(fileMd5);
        GetObjectArgs build = GetObjectArgs.builder()
                .bucket(videoFiles)
                .object(chunkFilePath + chunk)
                .build();
        try {
            FilterInputStream inputStream = minioClient.getObject(build);
            if (inputStream != null) {
                return RestResponse.success(true);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return RestResponse.success(false);
    }

    //得到分块文件的目录
    private String getChunkFilePath(String fileMd5) {
        return fileMd5.charAt(0) + "/" + fileMd5.charAt(1) + "/" + fileMd5 + "/" + "chunk" + "/";
    }

    @Override
    public RestResponse<Boolean> uploadChunk(MultipartFile file, String fileMd5, Integer chunk) {
        if (file == null || StringUtils.isEmpty(fileMd5) || chunk == null) {
            throw new ZhixuePlusException("参数为null");
        }
        //上传分块文件
        String mineType = getMimeType(null);
        String objectName = getChunkFilePath(fileMd5) + chunk;
        try {
            File tempFile = File.createTempFile("minio", ".temp");
            file.transferTo(tempFile);
            String path = tempFile.getAbsolutePath();
            boolean result = upLoadToMinio(videoFiles, path, objectName, mineType);
            return result ? RestResponse.success(result) : RestResponse.validfail(false, "上传分块文件失败");
        } catch (Exception e) {
            throw new ZhixuePlusException(e.getMessage());
        }
    }

    //合并文件并落库
    @Override
    public RestResponse mergeChunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {
        //分块文件所在的目录
        String chunkFilePath = getChunkFilePath(fileMd5);
        List<ComposeSource> sources = Stream.iterate(0, i -> ++i).limit(chunkTotal).map(i -> ComposeSource.builder().bucket(videoFiles).object(chunkFilePath + i).build()).collect(Collectors.toList());
        //找到分块文件进行合并
        String filename = uploadFileParamsDto.getFilename();
        String extension = filename.substring(filename.lastIndexOf("."));
        String objectName = getFileByMd5(fileMd5, extension);
        ComposeObjectArgs build = ComposeObjectArgs.builder()
                .bucket(videoFiles)
                .object(objectName)
                .sources(sources)
                .build();
        try {
            minioClient.composeObject(build);
        } catch (Exception e) {
            e.printStackTrace();
            return RestResponse.validfail(false, "合并文件出错");
        }
        //校验合并后的文件和源文件是否一致
        File file = downloadFileFromMinIO(videoFiles, objectName);
        //计算合并后的MD5
//        try (FileInputStream fileInputStream = new FileInputStream(file)) {
//            String mergeFile_md5 = DigestUtils.md5Hex(fileInputStream);
//            if (!mergeFile_md5.equals(fileMd5)) {
//                return RestResponse.validfail(false, "文件校验失败");
//            }
//        } catch (Exception e) {
//            e.printStackTrace();
//            return RestResponse.validfail(false, "文件校验失败");
//        }
        //文件的大小
        uploadFileParamsDto.setFileSize(file.length());
        //文件落库
        mediaFilesService.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, videoFiles, objectName);
        //清理分块文件
        clearChunkFiles(chunkFilePath, chunkTotal);
        return RestResponse.success(true);
    }

    private String getFileByMd5(String fileMd5, String fileExt) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + fileMd5 + fileExt;
    }

    @Override
    public File downloadFileFromMinIO(String bucket, String objectName) {
        //临时文件
        File minioFile = null;
        FileOutputStream outputStream = null;
        try {
            InputStream stream = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .build());
            //创建临时文件
            minioFile = File.createTempFile("minio", ".merge");
            outputStream = new FileOutputStream(minioFile);
            IOUtils.copy(stream, outputStream);
            return minioFile;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    //清理分块文件
    private void clearChunkFiles(String chunkFileFolderPath, int chunkTotal) {
        Iterable<DeleteObject> objects = Stream.iterate(0, i -> ++i).limit(chunkTotal).map(i -> new DeleteObject(chunkFileFolderPath + i)).collect(Collectors.toList());
        RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(videoFiles).objects(objects).build();
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
        //要想真正删除
        results.forEach(f -> {
            try {
                DeleteError deleteError = f.get();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    @Override
    public RestResponse<String> getFileById(String id) {
        if (StringUtils.isEmpty(id)) {
            throw new RuntimeException("参数为null");
        }
        MediaFiles mediaFiles = mediaFilesMapper.selectById(id);
        if (mediaFiles == null) {
            return RestResponse.validfail("媒资不存在");
        }
        if (StringUtils.isEmpty(mediaFiles.getUrl())) {
            return RestResponse.validfail("视频处理中");
        }
        return RestResponse.success(mediaFiles.getUrl());
    }
}
